package zju.xmsx.acm.search.back.dao;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import zju.xmsx.acm.search.back.dto.SearchFilter;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Component
public class ElasticSearcherImpl implements ElasticSearcher{
    @Resource
    @Qualifier("client")
    private RestHighLevelClient client;

    @Override
    public SearchHits searchByTitleKeyWord(String keys, int offset, int size){
//        模糊查询并从各文章类索引获取查询结果
        QueryBuilder query = new BoolQueryBuilder()
                .must(QueryBuilders.matchQuery("title", keys).fuzziness(Fuzziness.AUTO))
                .must(new BoolQueryBuilder()
                        .should(QueryBuilders.termQuery("_index", "article"))
                        .should(QueryBuilders.termQuery("_index", "inproceedings"))
                        .should(QueryBuilders.termQuery("_index", "book"))
                        .should(QueryBuilders.termQuery("_index", "incollection"))
                        .should(QueryBuilders.termQuery("_index", "phdthesis"))
                        .should(QueryBuilders.termQuery("_index", "mastersthesis"))
                        .should(QueryBuilders.termQuery("_index", "www")));

        String[] fields = new String[1];
        fields[0] = "title";
        return execQuerySearch(buildSource(offset, size, query, buildHighlight(fields)));
    }

    @Override
    public SearchHits searchByTitleKeyWord(String keys, int offset, int size, SearchFilter filter){
//        使用QueryBuilder构建查询参数，QueryBuilder包括所有Elasticsearch’s Query DSL支持的类型。
        QueryBuilder query1;
        if (filter.isExactMatch()){
//            精确匹配查询条件
            query1 = new BoolQueryBuilder()
                    .must(QueryBuilders.matchPhraseQuery("title", keys));
        }else{
//            模糊查询条件
            query1 = new BoolQueryBuilder()
                    .must(QueryBuilders.matchQuery("title", keys).fuzziness(Fuzziness.AUTO));
        }

        QueryBuilder query2;
        if (filter.getAuthor()!=null && !filter.getAuthor().isEmpty()){
            query2 = new BoolQueryBuilder()
                    .should(QueryBuilders.matchQuery("author", filter.getAuthor()))
                    .should(QueryBuilders.matchQuery("editor", filter.getAuthor()));
        }else{
            query2 = new BoolQueryBuilder().should(QueryBuilders.matchAllQuery());
        }

        QueryBuilder query3;
        if (filter.getPublicationType()!=null && !filter.getPublicationType().isEmpty()){
            query3 = new BoolQueryBuilder()
                    .must(QueryBuilders.matchQuery("_index", filter.getPublicationType()));
        }else {
            query3 = new BoolQueryBuilder()
                    .should(QueryBuilders.termQuery("_index", "article"))
                    .should(QueryBuilders.termQuery("_index", "inproceedings"))
                    .should(QueryBuilders.termQuery("_index", "book"))
                    .should(QueryBuilders.termQuery("_index", "incollection"))
                    .should(QueryBuilders.termQuery("_index", "phdthesis"))
                    .should(QueryBuilders.termQuery("_index", "mastersthesis"))
                    .should(QueryBuilders.termQuery("_index", "www"));
        }

        QueryBuilder query4;
        if (filter.getYearInterval()!=null && filter.getYearInterval().length==2){
            query4 = new BoolQueryBuilder()
                    .must(QueryBuilders.rangeQuery("year")
                            .from(filter.getYearInterval()[0]).to(filter.getYearInterval()[1]));
        }else {
            query4 = new BoolQueryBuilder().should(QueryBuilders.matchAllQuery());
        }

        QueryBuilder query5;
        if (filter.getPublicationTitle()!=null && !filter.getPublicationTitle().isEmpty()){
            query5 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("booktitle", filter.getPublicationTitle()));
        }else{
            query5 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder mainQuery = new BoolQueryBuilder()
                .must(query1).must(query2).must(query3).must(query4).must(query5);

        String[] fields = new String[1];
        fields[0] = "title";
        return execQuerySearch(buildSource(offset, size, mainQuery, buildHighlight(fields)), filter);
    }

    @Override
    public SearchHits searchAuthorList(String keys, int offset, int size) {
        QueryBuilder query = new BoolQueryBuilder()
                .must(QueryBuilders.termQuery("_index", "www"))
                .must(QueryBuilders.matchPhraseQuery("title", "Home Page"))
                .must(QueryBuilders.matchQuery("author", keys));

        return execQuerySearch(buildSource(offset, size, query, null));
    }

    @Override
    public SearchHits searchByAuthorExact(String keys, int offset, int size) {
//        matchPhraseQuery，不分词的精确匹配
        QueryBuilder query = QueryBuilders.boolQuery()
                .mustNot(QueryBuilders.matchPhraseQuery("title", "Home Page"))
                .must(QueryBuilders.matchPhraseQuery("author", keys));

        return execQuerySearch(buildSource(offset, size, query, null));
    }

    @Override
    public SearchHits searchByAuthorExact(String keys, int offset, int size, SearchFilter filter) {
        QueryBuilder query = QueryBuilders.boolQuery()
                .mustNot(QueryBuilders.matchPhraseQuery("title", "Home Page"))
                .must(QueryBuilders.matchPhraseQuery("author", keys));

        QueryBuilder query1;
        if (filter.getPublicationTitle()!=null && !filter.getPublicationTitle().isEmpty()){
            query1 = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("title", filter.getPublicationTitle()))
                    .should(QueryBuilders.matchQuery("booktitle", filter.getPublicationTitle()))
                    .should(QueryBuilders.matchQuery("journal", filter.getPublicationTitle()));
        }else{
            query1 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder query2;
        if (filter.getPublicationType()!=null && !filter.getPublicationType().isEmpty()){
            query2 = QueryBuilders.termQuery("_index", filter.getPublicationType());
        }else {
            query2 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder query3;
        if (filter.getYearInterval()!=null && filter.getYearInterval().length==2){
            query3 = QueryBuilders.rangeQuery("year")
                    .from(filter.getYearInterval()[0]).to(filter.getYearInterval()[1]);
        }else{
            query3 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder mainQuery = QueryBuilders.boolQuery()
                .must(query).must(query1).must(query2).must(query3);

        return execQuerySearch(buildSource(offset, size, mainQuery, null), filter);
    }

    @Override
    public SearchHits searchConferenceByKW(String keys, int offset, int size) {
        QueryBuilder query = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("_index", "proceedings"))
                .must(QueryBuilders.matchQuery("title", keys));

        String[] fields = new String[1];
        fields[0] = "title";
        return execQuerySearch(buildSource(offset, size, query, buildHighlight(fields)));
    }

    @Override
    public SearchHits searchConferenceByKW(String keys, int offset, int size, SearchFilter filter) {
        QueryBuilder query0;
        if (filter.isExactMatch()){
            query0 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("_index", "proceedings"))
                    .must(QueryBuilders.matchPhraseQuery("title", keys));
        }else{
            query0 = QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("_index", "proceedings"))
                    .must(QueryBuilders.matchQuery("title", keys));
        }

        QueryBuilder query1;
        if (filter.getYearInterval()!=null && filter.getYearInterval().length==2){
            query1 = QueryBuilders.rangeQuery("year")
                    .from(filter.getYearInterval()[0]).to(filter.getYearInterval()[1]);
        }else{
            query1 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder query = QueryBuilders.boolQuery()
                .must(query0).must(query1);

        String[] fields = new String[1];
        fields[0] = "title";
        return execQuerySearch(buildSource(offset, size, query, buildHighlight(fields)), filter);
    }

    @Override
    public SearchHits searchByConfExact(String keys, int offset, int size) {
        QueryBuilder query = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("_index", "inproceedings"))
                .must(QueryBuilders.matchPhraseQuery("crossref", keys));

        SearchSourceBuilder source = new SearchSourceBuilder();
        source.from(offset);
        source.size(size);
        source.timeout(new TimeValue(20, TimeUnit.SECONDS));

        source.query(query);

        return execQuerySearch(source);
    }

    @Override
    public SearchHits searchByConfExact(String keys, int offset, int size, SearchFilter filter) {
        QueryBuilder query0 = QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("_index", "inproceedings"))
                .must(QueryBuilders.matchPhraseQuery("crossref", keys));

        QueryBuilder query1;
        if (filter.getAuthor()!=null && !filter.getAuthor().isEmpty()){
            query1 = QueryBuilders.matchQuery("author", filter.getAuthor());
        }else{
            query1 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder query2;
        if (filter.getPublicationTitle()!=null && !filter.getPublicationTitle().isEmpty()){
            query2 = QueryBuilders.boolQuery()
                    .should(QueryBuilders.matchQuery("title", filter.getPublicationTitle()))
                    .should(QueryBuilders.matchQuery("booktitle", filter.getPublicationTitle()));
        }else{
            query2 = QueryBuilders.matchAllQuery();
        }

        QueryBuilder query = QueryBuilders.boolQuery()
                .must(query0).must(query1).must(query2);

        SearchSourceBuilder source = new SearchSourceBuilder();
        source.from(offset);
        source.size(size);
        source.timeout(new TimeValue(20, TimeUnit.SECONDS));

        source.query(query);

        return execQuerySearch(source, filter);
    }

    @Override
    public SearchHits mixedSearch(String keys, int offset, int size) {
        QueryBuilder query = QueryBuilders.boolQuery()
                .should(QueryBuilders.matchQuery("title", keys))
                .should(QueryBuilders.matchQuery("booktitle", keys))
                .should(QueryBuilders.matchQuery("author", keys));

        SearchSourceBuilder source = new SearchSourceBuilder();
        source.from(offset);
        source.size(size);
        source.timeout(new TimeValue(20, TimeUnit.SECONDS));

        source.query(query);

        return execQuerySearch(source);
    }

    private HighlightBuilder buildHighlight(String[] fields){
        HighlightBuilder highlight = new HighlightBuilder();
        highlight.requireFieldMatch(false);
        for (String field: fields){
            highlight.field(field);
        }
        highlight.preTags("<span class='highlight'>");
        highlight.postTags("</span>");

        return highlight;
    }

    private SearchSourceBuilder buildSource(int offset, int size, QueryBuilder query, HighlightBuilder highlight){
        SearchSourceBuilder source = new SearchSourceBuilder();
        source.from(offset);
        source.size(size);
        source.timeout(new TimeValue(20, TimeUnit.SECONDS));

        source.query(query);
        if (highlight != null){
            source.highlighter(highlight);
        }

        return source;
    }

    /**
     *  执行查询的流程框架
     * @param source 根据需求构建的查询器
     * @return 返回查询结果
     */
    private SearchHits execQuerySearch(SearchSourceBuilder source){
        return execQuerySearch(source, null);
    }

    private SearchHits execQuerySearch(SearchSourceBuilder source, SearchFilter filter){
        if (filter!=null && filter.getOrderBy()!=null && !filter.getOrderBy().isEmpty()){
            if (filter.isAscOrder()){
                source.sort(SortBuilders.fieldSort(filter.getOrderBy()+".keyword").order(SortOrder.ASC));
            }else{
                source.sort(SortBuilders.fieldSort(filter.getOrderBy()+".keyword").order(SortOrder.DESC));
            }
            source.sort(SortBuilders.scoreSort());
        }

        SearchRequest searchRequest = new SearchRequest();
        searchRequest.source(source);
//        执行查询
        try {
//            同步方式，程序将阻塞直至查询完成，可能抛出IO异常
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//            高亮片段替换
            for (SearchHit hit: searchResponse.getHits().getHits()){
                for (String key: hit.getHighlightFields().keySet()){
                    HighlightField content = hit.getHighlightFields().get(key);
                    if (content != null){
                        StringBuilder sb = new StringBuilder();
                        for (Text frag: content.fragments()){
                            sb.append(frag);
                        }
                        hit.getSourceAsMap().put("origin"+key, hit.getSourceAsMap().get(key));
                        hit.getSourceAsMap().put(key, sb.toString());
                    }
                }
            }
            return searchResponse.getHits();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
